package com.onlyxiahui.query.mybatis.dao;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.SqlSessionUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.util.StringUtils;

public class GenericDAO {

	protected final DefaultConversionService conversionService = new DefaultConversionService();
	protected Map<Object, List<PropertyDescriptor>> writeMethodPropertyDescriptorListMap = new HashMap<Object, List<PropertyDescriptor>>();

	public GenericDAO() {
		Converter<String, Date> converter = new Converter<String, Date>() {
			SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

			@Override
			public Date convert(String text) {
				Date date = null;
				text = text.replaceAll("[/\\.]", "-");// .,/格式全部转化成-间隔
				if (text.matches("\\d{4}")) {// 只有年份，补全月日时分秒
					text += "-01-01 00:00:00";
				} else if (text.matches("\\d{4}-\\d{1,2}")) {// 只有年月，补全日时分秒
					text += text + "-01 00:00:00";
				}
				if (text.matches("\\d{4}-\\d{1,2}-\\d{1,2}")) {// 只有年月日，补全时分秒
					text += " 00:00:00";
				} else if (text.matches("\\d{4}-\\d{1,2}-\\d{1,2} \\d{1,2}")) {// 只有年月日时，补全分秒
					text += ":00:00";
				} else if (text.matches("\\d{4}-\\d{1,2}-\\d{1,2} \\d{1,2}:\\d{1,2}")) {// 只有年月日时分，补全秒
					text += ":00";
				}
				try {
					df.parse(text);
				} catch (ParseException e) {
					e.printStackTrace();
				}
				return date;
			}
		};
		conversionService.addConverter(String.class, Date.class, converter);
	}

	// 一次批量处理的记录数
	private int batchSize = 500;

	public int getBatchSize() {
		return batchSize;
	}

	public void setBatchSize(int batchSize) {
		if (batchSize < 1) {
			throw new IllegalArgumentException("batchSize不能小于1");
		}
		this.batchSize = batchSize;
	}

	protected SqlSessionTemplate sqlSessionTemplate;

	public SqlSessionTemplate getSqlSessionTemplate() {
		return sqlSessionTemplate;
	}

	public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
		this.sqlSessionTemplate = sqlSessionTemplate;
	}

	public boolean hasTable(String tableName) {
		boolean result = false;
		try {
			Connection conn = getConnection();
			DatabaseMetaData meta = conn.getMetaData();
			ResultSet set = meta.getTables(null, null, tableName, null);
			if (set.next()) {
				result = true;
			}
		} catch (SQLException e) {
			throw new RuntimeException(e);
		}
		return result;
	}

	public Connection getConnection() throws SQLException {
		// return
		// sqlSessionTemplate.getConfiguration().getEnvironment().getDataSource().getConnection();
		SqlSessionFactory sqlSessionFactory = sqlSessionTemplate.getSqlSessionFactory();
		ExecutorType executorType = sqlSessionTemplate.getExecutorType();
		PersistenceExceptionTranslator exceptionTranslator = sqlSessionTemplate.getPersistenceExceptionTranslator();
		return SqlSessionUtils.getSqlSession(sqlSessionFactory, executorType, exceptionTranslator).getConnection();
	}

	public void insert(String statement, Object parameterObject) {
		sqlSessionTemplate.insert(statement, parameterObject);
	}

	public int insertReturnId(String statement, Object parameterObject) {
		return sqlSessionTemplate.insert(statement, parameterObject);
	}

	public int update(String statement, Object parameterObject) {
		return sqlSessionTemplate.update(statement, parameterObject);
	}

	public int delete(String statement, Object parameterObject) {
		return sqlSessionTemplate.delete(statement, parameterObject);
	}

	/**
	 * 批量插入
	 * 
	 * @param namespace
	 * @param statement
	 * @param list
	 */
	public void batchInsert(final String statement, final List<?> list) {
		SqlSession session = sqlSessionTemplate.getSqlSessionFactory().openSession(ExecutorType.BATCH, false);
		try {
			for (int i = 0, size = list.size(); i < size; i++) {
				session.insert(statement, list.get(i));
				if (i % batchSize == 0 || i == size - 1) {
					session.commit();
					session.clearCache();
				}
			}
		} catch (Exception e) {
			session.rollback();
			throw new RuntimeException(e);
		} finally {
			session.close();
		}
	}

	/**
	 * 批量删除
	 * 
	 * @param namespace
	 * @param statement
	 * @param list
	 */
	public void batchDelete(final String statement, final List<?> list) {
		SqlSession session = sqlSessionTemplate.getSqlSessionFactory().openSession(ExecutorType.BATCH, false);
		try {
			for (int i = 0, size = list.size(); i < size; i++) {
				session.delete(statement, list.get(i));
				if (i % batchSize == 0 || i == size - 1) {
					session.commit();
					session.clearCache();
				}
			}
		} catch (Exception e) {
			session.rollback();
			throw new RuntimeException(e);
		} finally {
			session.close();
		}
	}

	/**
	 * 批量修改
	 * 
	 * @param namespace
	 * @param statement
	 * @param list
	 */
	public void batchUpdate(final String statement, final List<?> list) {
		SqlSession session = sqlSessionTemplate.getSqlSessionFactory().openSession(ExecutorType.BATCH, false);
		try {
			for (int i = 0, size = list.size(); i < size; i++) {
				session.update(statement, list.get(i));
				if (i % batchSize == 0 || i == size - 1) {
					session.commit();
					session.clearCache();
				}
			}
		} catch (Exception e) {
			session.rollback();
			throw new RuntimeException(e);
		} finally {
			session.close();
		}
	}

	public void inserBySQL(String sql) {
		this.insert("common.insertBySQL", sql);
	}

	public int updateBySQL(String sql) {
		return this.update("common.updateBySQL", sql);
	}

	public int deleteBySQL(String sql) {
		return this.delete("common.deleteBySQL", sql);
	}

	@SuppressWarnings("unchecked")
	public Map<String, Object> queryForObjectBySQL(String sql) {
		return this.queryForObject("common.selectBySQL", sql, Map.class);
	}

	@SuppressWarnings("rawtypes")
	public List<Map> queryForListBySQL(String sql) {
		return this.queryForList("common.selectBySQL", sql, Map.class);
	}

	public void batchInsertBySQL(List<String> sql) {
		this.batchInsert("common.insertBySQL", sql);
	}

	public void batchUpdateBySQL(List<String> sql) {
		this.batchInsert("common.updateBySQL", sql);
	}

	public void batchDeleteBySQL(List<String> sql) {
		this.batchInsert("common.deleteBySQL", sql);
	}

	/**
	 * 读取一个对象,判断从ibatis返回的对象是否是Map，如果是Map则把key全部转换成javabean的属性写法，
	 * 比如USER_CD转换成userCd
	 * 
	 * @param statement
	 *            sqlid
	 * @param parameterObject
	 *            参数
	 * @param clazz
	 *            返回类型
	 * @return 查询结果
	 */
	public <T> T queryForObject(String statement, Object parameterObject, Class<T> clazz) {
		return queryForObject(statement, parameterObject, clazz, true);
	}

	@SuppressWarnings("unchecked")
	public <T> T queryForObject(String statement, Object parameterObject, Class<T> clazz, boolean transformMapFieldname) {
		Object data = sqlSessionTemplate.selectOne(statement, parameterObject);
		if (data != null && data instanceof Map) {
			if (transformMapFieldname && Map.class.isAssignableFrom(clazz)) {//
				return (T) mapUnderscoreToCamelCase((Map<String, Object>) data);
			} else {
				return (T) mapToObject((Map<String, Object>) data, clazz);
			}
		} else {
			return (T) data;
		}
	}

	/**
	 * 读取一系列对象,如果是Map则把key全部转换成javabean的属性写法，比如USER_CD转换成userCd
	 * 
	 * @param statement
	 *            sqlid
	 * @param parameterObject
	 *            参数
	 * @param clazz
	 *            返回类型
	 * @return 查询结果
	 */
	public <T> List<T> queryForList(String statement, Object parameterObject, Class<T> clazz) {
		return queryForList(statement, parameterObject, clazz, true);
	}

	@SuppressWarnings("unchecked")
	public <T> List<T> queryForList(String statement, Object parameterObject, Class<T> clazz, boolean transformMapFieldname) {
		List<T> list = sqlSessionTemplate.selectList(statement, parameterObject);
		int size = list.size();
		if (size > 0 && (list.get(0) instanceof Map)) {// 当list的第一个对象是Map时才转换
			List<Object> t = new ArrayList<Object>(size);
			if (transformMapFieldname && Map.class.isAssignableFrom(clazz)) {//
				Map<String, Object> oldMap = null;
				for (int i = 0; i < size; i++) {
					oldMap = (Map<String, Object>) list.get(i);
					if (oldMap != null) {
						t.add(mapUnderscoreToCamelCase(oldMap));
					}
				}
			} else {
				Map<String, Object> map = null;
				for (int i = 0; i < size; i++) {
					map = (Map<String, Object>) list.get(i);
					if (map != null) {
						t.add(mapToObject((Map<String, Object>) map, clazz));
					}
				}
			}
			return (List<T>) t;
		} else {
			return (List<T>) list;
		}
	}

	/**
	 * 分页查询，如果是Map则把key全部转换成javabean的属性写法，用驼峰命名，比如USER_CD转换成userCd
	 * 
	 * @param statement
	 *            sqlid
	 * @param param
	 *            参数
	 * @param pageIndex
	 *            第几页
	 * @param limit
	 *            一页的记录数
	 * @param clazz
	 *            返回类型
	 * @return 查询结果
	 */
	public <T> List<T> queryForPageList(String statement, Object parameterObject, int pageIndex, int limit, Class<T> clazz) {
		return queryForPageList(statement, parameterObject, pageIndex, limit, clazz, true);
	}

	@SuppressWarnings("unchecked")
	public <T> List<T> queryForPageList(String statement, Object parameterObject, int pageIndex, int limit, Class<T> clazz, boolean transformMapFieldname) {
		List<T> list = sqlSessionTemplate.selectList(statement, parameterObject, new RowBounds(pageIndex, limit));
		int size = list.size();
		if (size > 0 && (list.get(0) instanceof Map)) {// 当list的第一个对象是Map时才转换
			List<Object> t = new ArrayList<Object>(size);
			if (transformMapFieldname && Map.class.isAssignableFrom(clazz)) {//
				Map<String, Object> oldMap = null;
				for (int i = 0; i < size; i++) {
					oldMap = (Map<String, Object>) list.get(i);
					if (oldMap != null) {
						t.add(mapUnderscoreToCamelCase(oldMap));
					}
				}
			} else {
				Map<String, Object> map = null;
				for (int i = 0; i < size; i++) {
					map = (Map<String, Object>) list.get(i);
					if (map != null) {
						t.add(mapToObject((Map<String, Object>) map, clazz));
					}
				}
			}
			return (List<T>) t;
		} else {
			return (List<T>) list;
		}
	}

	/**
	 * 将Map的key转成驼峰命名的方式
	 * 
	 * @param oldMap
	 * @return
	 */
	private Map<String, Object> mapUnderscoreToCamelCase(Map<String, Object> oldMap) {
		Map<String, Object> newMap = null;
		if (oldMap != null) {
			newMap = new HashMap<String, Object>(oldMap.size());
			// 循环已有的Map，取出Key转换到javabean属性名
			Iterator<Entry<String, Object>> iterator = oldMap.entrySet().iterator();
			while (iterator.hasNext()) {
				Map.Entry<String, Object> entry = (Map.Entry<String, Object>) iterator.next();
				newMap.put(filedNameToJavaName(entry.getKey().toString()), entry.getValue());
			}
		}
		return newMap;
	}

	/**
	 * 表字段名转换java的属性，去掉下划线，比如字段名USER_NAME，属性名为userName
	 * 
	 * @param fileName
	 * @return
	 */
	String filedNameToJavaName(String fileName) {
		String t = tableNameToClassName(fileName);
		return t.substring(0, 1).toLowerCase() + t.substring(1);
	}

	String tableNameToClassName(String tablename) {
		String[] sa = tablename.split("[_]");
		StringBuffer sb = new StringBuffer();

		for (int i = 0; i < sa.length; ++i) {
			sb.append(firstUpper(sa[i]));
		}

		return sb.toString();
	}

	/**
	 * 输入字符串，将第一个字母变成大写，通常用在类名
	 * 
	 * @param s
	 *            字符串
	 * @return
	 */
	String firstUpper(String s) {
		String lowStr = s.toLowerCase();
		return lowStr.substring(0, 1).toUpperCase() + lowStr.substring(1);
	}

	List<Object> mapListToObjectList(List<Map<String, Object>> mapList, Class<?> classType) {
		List<Object> list = new ArrayList<Object>(mapList.size());
		for (Map<String, Object> map : mapList) {
			list.add(mapToObject(map, classType));
		}
		return list;
	}

	Object mapToObject(Map<String, Object> dataMap, Class<?> classType) {
		return resolver(classType, dataMap);
	}

	/**
	 * 获取类中有set方法是属性
	 * 
	 * @param classType
	 * @return
	 */
	public List<PropertyDescriptor> getWriteMethodPropertyDescriptorList(Class<?> classType) {
		List<PropertyDescriptor> propertyDescriptorList = writeMethodPropertyDescriptorListMap.get(classType);
		try {
			if (null == propertyDescriptorList) {
				propertyDescriptorList = new ArrayList<PropertyDescriptor>();
				BeanInfo bi = Introspector.getBeanInfo(classType, Object.class);
				PropertyDescriptor[] pds = bi.getPropertyDescriptors();
				for (PropertyDescriptor pd : pds) {
					if (null != pd.getWriteMethod()) {
						propertyDescriptorList.add(pd);
					}
				}
				writeMethodPropertyDescriptorListMap.put(classType, propertyDescriptorList);
			}
		} catch (IntrospectionException e) {
			throw new RuntimeException(e);
		}
		return propertyDescriptorList;
	}

	public Object resolver(Class<?> classType, Map<String, Object> dataMap) {

		Map<String, Object> map = new HashMap<String, Object>();
		Set<String> keySet = dataMap.keySet();

		for (String key : keySet) {
			String name = key.replace("-", "").toLowerCase();
			map.put(name, dataMap.get(key));
		}

		Object object = null;
		try {
			object = classType.newInstance();// Class.forName(classType.getName()).newInstance();

			BeanWrapper beanWrapper = new BeanWrapperImpl(object);
			SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
			beanWrapper.registerCustomEditor(Date.class, null, new AllCustomDateEditor(df, true));

			List<PropertyDescriptor> propertyDescriptorList = this.getWriteMethodPropertyDescriptorList(classType);
			for (PropertyDescriptor pd : propertyDescriptorList) {
				String propertyName = pd.getName();
				String key = propertyName.replace("-", "").toLowerCase();
				Object value = map.get(key);
				if (null != value) {
					if (conversionService.canConvert(value.getClass(), pd.getPropertyType())) {
						value = conversionService.convert(value, pd.getPropertyType());
					}
					beanWrapper.setPropertyValue(propertyName, value);
				}
			}
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (InstantiationException e) {
			e.printStackTrace();
		}
		return object;
	}

	class AllCustomDateEditor extends CustomDateEditor {

		private DateFormat dateFormat;
		private boolean allowEmpty;
		private int exactDateLength;

		public AllCustomDateEditor(DateFormat dateFormat, boolean allowEmpty) {
			super(dateFormat, allowEmpty);
			this.dateFormat = dateFormat;
			this.allowEmpty = allowEmpty;
			this.exactDateLength = -1;
		}

		public AllCustomDateEditor(DateFormat dateFormat, boolean allowEmpty, int exactDateLength) {
			super(dateFormat, allowEmpty);
			this.dateFormat = dateFormat;
			this.allowEmpty = allowEmpty;
			this.exactDateLength = exactDateLength;
		}

		@Override
		public void setAsText(String text) throws IllegalArgumentException {
			if (this.allowEmpty && !StringUtils.hasText(text)) {
				// Treat empty String as null value.
				setValue(null);
			} else if (text != null && this.exactDateLength >= 0 && text.length() != this.exactDateLength) {
				throw new IllegalArgumentException("Could not parse date: it is not exactly" + this.exactDateLength + "characters long");
			} else {
				try {
					text = text.replaceAll("[/\\.]", "-");// .,/格式全部转化成-间隔
					if (text.matches("\\d{4}")) {// 只有年份，补全月日时分秒
						text += "-01-01 00:00:00";
					} else if (text.matches("\\d{4}-\\d{1,2}")) {// 只有年月，补全日时分秒
						text += text + "-01 00:00:00";
					}
					if (text.matches("\\d{4}-\\d{1,2}-\\d{1,2}")) {// 只有年月日，补全时分秒
						text += " 00:00:00";
					} else if (text.matches("\\d{4}-\\d{1,2}-\\d{1,2} \\d{1,2}")) {// 只有年月日时，补全分秒
						text += ":00:00";
					} else if (text.matches("\\d{4}-\\d{1,2}-\\d{1,2} \\d{1,2}:\\d{1,2}")) {// 只有年月日时分，补全秒
						text += ":00";
					}

					setValue(this.dateFormat.parse(text));
				} catch (ParseException ex) {
					throw new IllegalArgumentException("Could not parse date: " + ex.getMessage(), ex);
				}
			}
		}
	}
}
